﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace BibMan.model.database.bibtex {
    public class BibParser {

        public String BibTex { get; private set; }
        private StringReader reader;
        public StringBuilder ErrorText { get; private set; }

        bool EOF {
            get {
                return reader.Peek() == -1;
            }
        }

        public BibParser(String bibTex) {
            this.BibTex = bibTex;
            reader = new StringReader(bibTex);
            ErrorText = new StringBuilder();
        }

        private void SkipWhitespace() {
            while (CheckWhitespace()) {
                Read(); //skip
            }
        }

        private bool CheckWhitespace() {
            if (EOF) return false;
            return Char.IsWhiteSpace(Peek());
        }

        private char Peek() {
            return Convert.ToChar(reader.Peek());
        }

        private char Read() {
            return Convert.ToChar(reader.Read());
        }

        private BibType ParseType() {
            if ((Convert.ToChar(Read())).Equals('@')) {
                StringBuilder type = new StringBuilder();
                while (!Peek().Equals('{')) {
                    type.Append(Read());
                }
                BibType ret;
                if (Enum.TryParse<BibType>(type.ToString().Trim(), true, out ret)) {
                    return ret;
                } else {
                    ErrorText.AppendLine("BibType was not parsed.");
                }
            }
            
            return BibType.misc;
        }

        private String ParseId() {
            StringBuilder id = new StringBuilder();
            while (!Peek().Equals(',')) {
                id.Append(Read());
            }
            return id.ToString().Trim();
        }

        private Tuple<String, String> ParseTag() {
            StringBuilder tagName = new StringBuilder();
            while (!Peek().Equals('=')) {
                tagName.Append(Read());
            }
            
            if (ErrorCheck('=')) return null;

            StringBuilder tagContent = new StringBuilder();
            if (Find('{')) {
                while (!Peek().Equals('}')) {
                    tagContent.Append(Read());
                }
                Find('"', '}'); //read the close bit of the tag
            } else if (Find('"')) { //open normal
                while (!Peek().Equals('"')) { //close normal
                    tagContent.Append(Read());
                }
                Read();
                while (Find('#') && Find('"')) { //open concat
                    while (!Peek().Equals('"')) { //close concat
                        tagContent.Append(Read());
                    }
                }
                Find('"', '}'); //read the close bit of the tag
            } else if (Check('0','1','2','3','4','5','6','7','8','9')) {
                while (!CheckWhitespace() && !Peek().Equals(',') && !Peek().Equals('}')) { //close normal
                    tagContent.Append(Read());
                }
            }
            SkipWhitespace();

            return new Tuple<String, String>(tagName.ToString().Trim(), tagContent.ToString().Trim());
        }

        public BibItem Parse() {
            BibItem ret = new BibItem();

            SkipWhitespace();
            ret.BibType = ParseType();
            if (!ErrorCheck('{')) {
                SkipWhitespace();
                ret.Id = ParseId();

                while (!ErrorCheck(',') && !Find('}')) {
                    Console.WriteLine(Peek());
                    Tuple<String, String> tag = ParseTag();
                    ret[tag.Item1] = tag.Item2;
                }
            }

            return ret;
        }

        private bool Check(params char[] token) {
            SkipWhitespace();
            if (!token.Contains(Peek())) {
                return false;
            } else {
                return true;
            }
        }

        private bool Find(params char[] token) {
            SkipWhitespace();
            char c = Peek();
            if (!token.Contains(Peek())) {
                return false;
            } else {
                Read();
                SkipWhitespace();
                return true;
            }
        }

        private bool ErrorCheck(params char[] token) {
            SkipWhitespace();
            if (!token.Contains(Read())) {
                ErrorText.Append("Unable to parse, looking for token: ");
                foreach (char c in token) {
                    ErrorText.Append(c).Append(" ");
                }
                ErrorText.AppendLine();
                return true;
            } else {
                SkipWhitespace();
                return false;
            }
        }
    }
}
